#include "glare.math.AngleUtils.h"

namespace Glare {

	int AngleUtils::mTrigTableSize;
	AngleUtils::AngleUnit AngleUtils::msAngleUnit;

	Real  AngleUtils::mTrigTableFactor;
	Real *AngleUtils::mSinTable = NULL;
	Real *AngleUtils::mTanTable = NULL;


	//-----------------------------------------------------------------------
	AngleUtils::AngleUtils( unsigned int trigTableSize )
	{
		msAngleUnit = AU_DEGREE;
		mTrigTableSize = trigTableSize;
		mTrigTableFactor = mTrigTableSize / Angle::TWO_PI;

		//mSinTable = OGRE_ALLOC_T(Real, mTrigTableSize, MEMCATEGORY_GENERAL);
		//mTanTable = OGRE_ALLOC_T(Real, mTrigTableSize, MEMCATEGORY_GENERAL);

		mSinTable = new Real[mTrigTableSize];
		mTanTable = new Real[mTrigTableSize];

		buildTrigTables();
	}

	//-----------------------------------------------------------------------
	AngleUtils::~AngleUtils()
	{
		//OGRE_FREE(mSinTable, MEMCATEGORY_GENERAL);
		//OGRE_FREE(mTanTable, MEMCATEGORY_GENERAL);

		delete[] mSinTable;
		delete[] mTanTable;
	}

	//-----------------------------------------------------------------------
	void AngleUtils::buildTrigTables(void)
	{
		// Build trig lookup tables
		// Could get away with building only PI sized Sin table but simpler this 
		// way. Who cares, it'll ony use an extra 8k of memory anyway and I like 
		// simplicity.
		Real angle;
		for (int i = 0; i < mTrigTableSize; ++i)
		{
			angle = Angle::TWO_PI * i / mTrigTableSize;
			mSinTable[i] = sin(angle);
			mTanTable[i] = tan(angle);
		}
	}
	//-----------------------------------------------------------------------	
	Real AngleUtils::SinTable (Real fValue)
	{
		// Convert range to index values, wrap if required
		int idx;
		if (fValue >= 0)
		{
			idx = int(fValue * mTrigTableFactor) % mTrigTableSize;
		}
		else
		{
			idx = mTrigTableSize - (int(-fValue * mTrigTableFactor) % mTrigTableSize) - 1;
		}

		return mSinTable[idx];
	}
	//-----------------------------------------------------------------------
	Real AngleUtils::TanTable (Real fValue)
	{
		// Convert range to index values, wrap if required
		int idx = int(fValue *= mTrigTableFactor) % mTrigTableSize;
		return mTanTable[idx];
	}
	//-----------------------------------------------------------------------
	Radian AngleUtils::ACos (Real fValue)
	{
		if ( -1.0 < fValue )
		{
			if ( fValue < 1.0 )
				return Radian(acos(fValue));
			else
				return Radian(0.0);
		}
		else
		{
			return Radian(Angle::PI);
		}
	}
	//-----------------------------------------------------------------------
	Radian AngleUtils::ASin (Real fValue)
	{
		if ( -1.0 < fValue )
		{
			if ( fValue < 1.0 )
				return Radian(asin(fValue));
			else
				return Radian(Angle::HALF_PI);
		}
		else
		{
			return Radian(-Angle::HALF_PI);
		}
	}
	//-----------------------------------------------------------------------
	void AngleUtils::setAngleUnit(AngleUtils::AngleUnit unit)
	{
		msAngleUnit = unit;
	}
	//-----------------------------------------------------------------------
	AngleUtils::AngleUnit AngleUtils::getAngleUnit(void)
	{
		return msAngleUnit;
	}
	//-----------------------------------------------------------------------
	Real AngleUtils::AngleUnitsToRadians(Real angleunits)
	{
		if (msAngleUnit == AU_DEGREE)
			return angleunits * Angle::fDeg2Rad;
		else
			return angleunits;
	}

	//-----------------------------------------------------------------------
	Real AngleUtils::RadiansToAngleUnits(Real radians)
	{
		if (msAngleUnit == AU_DEGREE)
			return radians * Angle::fRad2Deg;
		else
			return radians;
	}

	//-----------------------------------------------------------------------
	Real AngleUtils::AngleUnitsToDegrees(Real angleunits)
	{
		if (msAngleUnit == AU_RADIAN)
			return angleunits * Angle::fRad2Deg;
		else
			return angleunits;
	}

	//-----------------------------------------------------------------------
	Real AngleUtils::DegreesToAngleUnits(Real degrees)
	{
		if (msAngleUnit == AU_RADIAN)
			return degrees * Angle::fDeg2Rad;
		else
			return degrees;
	}
	//-----------------------------------------------------------------------
	//Matrix4 AngleUtils::buildReflectionMatrix(const Plane& p)
	//{
	//	return Matrix4(
	//		-2 * p.normal.x * p.normal.x + 1,   -2 * p.normal.x * p.normal.y,       -2 * p.normal.x * p.normal.z,       -2 * p.normal.x * p.d, 
	//		-2 * p.normal.y * p.normal.x,       -2 * p.normal.y * p.normal.y + 1,   -2 * p.normal.y * p.normal.z,       -2 * p.normal.y * p.d, 
	//		-2 * p.normal.z * p.normal.x,       -2 * p.normal.z * p.normal.y,       -2 * p.normal.z * p.normal.z + 1,   -2 * p.normal.z * p.d, 
	//		0,                                  0,                                  0,                                  1);
	//}
	//-----------------------------------------------------------------------
	//Vector4 AngleUtils::calculateFaceNormal(const Vector3& v1, const Vector3& v2, const Vector3& v3)
	//{
	//	Vector3 normal = calculateBasicFaceNormal(v1, v2, v3);
	//	// Now set up the w (distance of tri from origin
	//	return Vector4(normal.x, normal.y, normal.z, -(normal.dotProduct(v1)));
	//}
	//-----------------------------------------------------------------------
	//Vector3 AngleUtils::calculateBasicFaceNormal(const Vector3& v1, const Vector3& v2, const Vector3& v3)
	//{
	//	Vector3 normal = (v2 - v1).crossProduct(v3 - v1);
	//	normal.normalise();
	//	return normal;
	//}
	//-----------------------------------------------------------------------
	//Vector4 AngleUtils::calculateFaceNormalWithoutNormalize(const Vector3& v1, const Vector3& v2, const Vector3& v3)
	//{
	//	Vector3 normal = calculateBasicFaceNormalWithoutNormalize(v1, v2, v3);
	//	// Now set up the w (distance of tri from origin)
	//	return Vector4(normal.x, normal.y, normal.z, -(normal.dotProduct(v1)));
	//}
	//-----------------------------------------------------------------------
	//Real AngleUtils::gaussianDistribution(Real x, Real offset, Real scale)
	//{
	//	Real nom = AngleUtils::Exp(
	//		-AngleUtils::Sqr(x - offset) / (2 * AngleUtils::Sqr(scale)));
	//	Real denom = scale * AngleUtils::Sqrt(2 * AngleUtils::PI);

	//	return nom / denom;

	//}
	//---------------------------------------------------------------------
	//Matrix4 AngleUtils::makeViewMatrix(const Vector3& position, const Quaternion& orientation, 
	//	const Matrix4* reflectMatrix)
	//{
	//	Matrix4 viewMatrix;

	//	// View matrix is:
	//	//
	//	//  [ Lx  Uy  Dz  Tx  ]
	//	//  [ Lx  Uy  Dz  Ty  ]
	//	//  [ Lx  Uy  Dz  Tz  ]
	//	//  [ 0   0   0   1   ]
	//	//
	//	// Where T = -(Transposed(Rot) * Pos)

	//	// This is most efficiently done using 3x3 Matrices
	//	Matrix3 rot;
	//	orientation.ToRotationMatrix(rot);

	//	// Make the translation relative to new axes
	//	Matrix3 rotT = rot.Transpose();
	//	Vector3 trans = -rotT * position;

	//	// Make final matrix
	//	viewMatrix = Matrix4::IDENTITY;
	//	viewMatrix = rotT; // fills upper 3x3
	//	viewMatrix[0][3] = trans.x;
	//	viewMatrix[1][3] = trans.y;
	//	viewMatrix[2][3] = trans.z;

	//	// Deal with reflections
	//	if (reflectMatrix)
	//	{
	//		viewMatrix = viewMatrix * (*reflectMatrix);
	//	}

	//	return viewMatrix;

	//}
	//---------------------------------------------------------------------
	//Real AngleUtils::boundingRadiusFromAABB(const AxisAlignedBox& aabb)
	//{
	//	Vector3 max = aabb.getMaximum();
	//	Vector3 min = aabb.getMinimum();

	//	Vector3 magnitude = max;
	//	magnitude.makeCeil(-max);
	//	magnitude.makeCeil(min);
	//	magnitude.makeCeil(-min);

	//	return magnitude.length();
	//}

}